﻿using System;
using System.Reflection;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Internal;
using ApplicationGenerator.BusinessModule.Interfaces;

namespace ApplicationGenerator.BusinessModule.MetaElements
{
    /// <summary>
    /// Klasa zarządzająca meta modelami.
    /// Singleton
    /// </summary>
    public class MetaModelManager
    {
        /// <summary>
        /// Kolekcja meta modeli
        /// </summary>
        public IMetaModelCollection MetaModelCollection
        {
            get;
            private set;
        }

        /// <summary>
        /// Kolekcja meta słowników
        /// </summary>
        public IMetaDictionaryCollection MetaDictionaryCollection
        {
            get;
            private set;
        }

        /// <summary>
        /// Instancja klasy zarządzającej meta modelami
        /// </summary>
        public static MetaModelManager Instance
        {
            get;
            private set;
        }

        /// <summary>
        /// Prywatny konstruktor
        /// </summary>
        static MetaModelManager()
        {
            Instance = new MetaModelManager();
        }

        private MetaModelManager()
        {
            MetaModelCollection = new MetaModelCollection();
            MetaDictionaryCollection = new MetaDictionaryCollection();

            ActiveRecordStarter.ModelsCreated += new ModelsCreatedDelegate(ActiveRecordStarter_ModelsCreated);
        }


        /// <summary>
        /// Handler dla zdarzenia wołanego po utworzeniu meta modeli
        /// </summary>
        /// <param name="models">Modele stworzone przez Active Record</param>
        /// <param name="source">Konfiguracja modeli</param>
        private void ActiveRecordStarter_ModelsCreated(ActiveRecordModelCollection models, IConfigurationSource source)
        {
            Assembly modelAssembly = null;
            foreach (ActiveRecordModel activeRecordModel in models)
            {
                if (modelAssembly == null)
                {
                    modelAssembly = activeRecordModel.Type.Assembly;
                }
                ConfigureMetaModel(activeRecordModel);
            }

            if (modelAssembly == null)
            {
                throw new Exception("Nie znaleziono żadnych modeli.");
            }

            ObtainMetaDictionaries(modelAssembly);
        }

        /// <summary>
        /// Tworzy i konfiguruje meta model
        /// </summary>
        /// <param name="activeRecordModel"></param>
        private void ConfigureMetaModel(ActiveRecordModel activeRecordModel)
        {
            MetaModel metaAdapter = new MetaModel();
            metaAdapter.ModelType = activeRecordModel.Type;
            metaAdapter.ModelName = activeRecordModel.Type.Name;

            if (activeRecordModel.PrimaryKey != null)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(activeRecordModel.PrimaryKey.Property.Name,
                                    activeRecordModel.PrimaryKey.Property.PropertyType,
                                    MetaFieldType.Property));
                //troche oszukany sposób, ale dopóki zakładamy, że id może być jedno to jest ok
                metaAdapter.PrimaryKeyName = activeRecordModel.PrimaryKey.Property.Name;
            }

            if (activeRecordModel.CompositeKey != null)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(activeRecordModel.CompositeKey.Property.Name,
                                    activeRecordModel.CompositeKey.Property.PropertyType,
                                    MetaFieldType.Property));
            }

            foreach (PropertyModel propertyModel in activeRecordModel.Properties)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(propertyModel.Property.Name,
                                    propertyModel.Property.PropertyType,
                                    (propertyModel.Property.PropertyType.IsEnum) ? MetaFieldType.Dictionary : MetaFieldType.Property));
            }


            foreach (BelongsToModel belongsToModel in activeRecordModel.BelongsTo)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(belongsToModel.Property.Name,
                                    belongsToModel.Property.PropertyType,
                                    MetaFieldType.Relation));
            }

            foreach (OneToOneModel oneToOneModel in activeRecordModel.OneToOnes)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(oneToOneModel.Property.Name,
                                    oneToOneModel.Property.PropertyType,
                                    MetaFieldType.List));
            }

            foreach (HasManyModel hasManyModel in activeRecordModel.HasMany)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(hasManyModel.Property.Name,
                                    hasManyModel.Property.PropertyType,
                                    MetaFieldType.List));
            }

            foreach (HasAndBelongsToManyModel hasAndBelongsToManyModel in activeRecordModel.HasAndBelongsToMany)
            {
                metaAdapter.MetaFieldCollection.Add(
                    CreateMetaField(hasAndBelongsToManyModel.Property.Name,
                                    hasAndBelongsToManyModel.Property.PropertyType,
                                    MetaFieldType.List));
            }

            MetaModelCollection.Add(metaAdapter);
        }

        /// <summary>
        /// Pobiera meta słowniki
        /// </summary>
        /// <param name="modelAssembly">Assembly z meta słownikami</param>
        private void ObtainMetaDictionaries(Assembly modelAssembly)
        {
            foreach (Module module in modelAssembly.GetModules())
            {
                foreach (Type type in module.GetTypes())
                {
                    if (type.IsEnum)
                    {
                        foreach (object customAttribute in type.GetCustomAttributes(true))
                        {
                            if (customAttribute is MetaDictionaryAttribute)
                            {
                                MetaDictionaryCollection.Add(CreateMetaDictionary(type));
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Tworzy meta słownik
        /// </summary>
        /// <param name="enumType"></param>
        /// <returns></returns>
        private IMetaDictionary CreateMetaDictionary(Type enumType)
        {
            MetaDictionary newMetaDictionary = new MetaDictionary();
            newMetaDictionary.DictionaryName = enumType.Name;
            newMetaDictionary.DictionaryType = enumType;

            MetaDictionaryItem metaDictionaryItem = null;

            foreach (FieldInfo enumEntryInfo in enumType.GetFields())
            {
                foreach (object customAttributte in enumEntryInfo.GetCustomAttributes(true))
                {
                    if (customAttributte is MetaEnumValueAttribute)
                    {
                        metaDictionaryItem = new MetaDictionaryItem();
                        metaDictionaryItem.DictionaryKey = enumEntryInfo.Name;
                        metaDictionaryItem.DictionaryValue = (customAttributte as MetaEnumValueAttribute).Value;

                        newMetaDictionary.MetaDictionaryItems.Add(metaDictionaryItem);
                    }
                }
            }

            return newMetaDictionary;
        }

        /// <summary>
        /// Tworzy meta właściwość
        /// </summary>
        /// <param name="fieldName">Nazwa meta właściwości</param>
        /// <param name="fieldType">Typ meta właściwości</param>
        /// <param name="metaFieldType">Rodzaj meta właściwości</param>
        /// <returns>Utworzona meta właściwość</returns>
        private IMetaField CreateMetaField(string fieldName, Type fieldType, MetaFieldType metaFieldType)
        {
            MetaField metaField = new MetaField();
            metaField.FieldName = fieldName;
            metaField.FieldType = fieldType;
            metaField.MetaFieldType = metaFieldType;

            return metaField;
        }
    }
}
